#include "dfs.h"
#include "dfs_inner.h"
#include "ff.h"
#include "ff_port.h"
#include "mem_sal.h"

/* fatfs dfs 接口 */


/* define */
#define TAG "FF-DFS"


/* static func */

/* FatFs API的返回值 */
static const char *ff_dfs_fatfs_ret_commit_tbl[] =
{
    "FR_OK:成功",                                           /* (0) Succeeded */
    "FR_DISK_ERR:底层硬件错误",                             /* (1) A hard error occurred in the low level disk I/O layer */
    "FR_INT_ERR:断言失败",                                  /* (2) Assertion failed */
    "FR_NOT_READY:物理驱动没有工作",                        /* (3) The physical drive cannot work */
    "FR_NO_FILE:文件不存在",                                /* (4) Could not find the file */
    "FR_NO_PATH:路径不存在",                                /* (5) Could not find the path */
    "FR_INVALID_NAME:无效文件名",                           /* (6) The path name format is invalid */
    "FR_DENIED:由于禁止访问或者目录已满访问被拒绝",         /* (7) Access denied due to prohibited access or directory full */
    "FR_EXIST:文件已经存在",                                /* (8) Access denied due to prohibited access */
    "FR_INVALID_OBJECT:文件或者目录对象无效",               /* (9) The file/directory object is invalid */
    "FR_WRITE_PROTECTED:物理驱动被写保护",                  /* (10) The physical drive is write protected */
    "FR_INVALID_DRIVE:逻辑驱动号无效",                      /* (11) The logical drive number is invalid */
    "FR_NOT_ENABLED:卷中无工作区",                          /* (12) The volume has no work area */
    "FR_NO_FILESYSTEM:没有有效的FAT卷",                     /* (13) There is no valid FAT volume */
    "FR_MKFS_ABORTED:由于参数错误f_mkfs()被终止",           /* (14) The f_mkfs() aborted due to any parameter error */
    "FR_TIMEOUT:在规定的时间内无法获得访问卷的许可",        /* (15) Could not get a grant to access the volume within defined period */
    "FR_LOCKED:由于文件共享策略操作被拒绝",                 /* (16) The operation is rejected according to the file sharing policy */
    "FR_NOT_ENOUGH_CORE:无法分配长文件名工作区",            /* (17) LFN working buffer could not be allocated */
    "FR_TOO_MANY_OPEN_FILES:当前打开的文件数大于_FS_SHARE", /* (18) Number of open files > _FS_SHARE */
    "FR_INVALID_PARAMETER:参数无效"                         /* (19) Given parameter is invalid */
};

static int ff_dfs_result_to_dfs(FRESULT ret)
{
    int sta = 0;

    if (ret > FR_INVALID_PARAMETER) ret = FR_INVALID_PARAMETER;
    switch (ret)
    {
    case FR_OK:
        sta = 0;
        break;

    case FR_NO_FILE:
    case FR_NO_PATH:
    case FR_NO_FILESYSTEM:
        sta = -ENOENT;
        break;

    case FR_INVALID_NAME:
        sta = -EINVAL;
        break;

    case FR_EXIST:
    case FR_INVALID_OBJECT:
        sta = -EEXIST;
        break;

    case FR_DISK_ERR:
    case FR_NOT_READY:
    case FR_INT_ERR:
        sta = -EIO;
        break;

    case FR_WRITE_PROTECTED:
    case FR_DENIED:
        sta = -EROFS;
        break;

    case FR_MKFS_ABORTED:
        sta = -EINVAL;
        break;

    default:
        sta = -1;
        break;
    }

    if (ret != 0)
    {
        // LOG_W(TAG, "FRESULT %d to %d", ret, sta);
        LOG_W(TAG, "FRESULT '%s' to %d", ff_dfs_fatfs_ret_commit_tbl[ret], sta);
    }

    return sta;
}

/* static func - file */

static int ff_dfs_file_open(struct dfs_fd *file)
{
    FIL *fd;
    DIR *dir;
    FRESULT ret;
    BYTE mode;
    int vol;
    char *driver_fn;

    if (file == NULL) return -ENOENT;

    driver_fn = ff_mem_malloc(256);
    if (driver_fn == NULL)
    {
        return -ENOMEM;
    }

    vol = disk_get_vol_by_name(file->fs->dev_name);
    if (vol < 0)
    {
        LOG_E(TAG, "get vol fail, dev_name '%s' can't found", file->fs->dev_name);
    }

    snprintf(driver_fn, 256, "%d:%s", vol, file->path);
    LOG_D(TAG, "open: %s, flag=0x%08X", driver_fn, file->flags);

    if (file->flags & O_DIRECTORY)
    {
        /* create directory */
        if (file->flags & O_CREAT)
        {
            ret = f_mkdir(driver_fn);
            if (ret != FR_OK)
            {
                ff_mem_free(driver_fn);
                return ff_dfs_result_to_dfs(ret);
            }
        }

        /* open directory */
        dir = (DIR*)ff_mem_malloc(sizeof(DIR));
        if (dir == NULL)
        {
            ff_mem_free(driver_fn);
            return -ENOMEM;
        }
        ret = f_opendir(dir, driver_fn);
        ff_mem_free(driver_fn);
        if (ret != FR_OK)
        {
            ff_mem_free(dir);
            return ff_dfs_result_to_dfs(ret);
        }

        file->data = dir;
        return 0;
    }
    else
    {
        mode = FA_READ;

        if (file->flags & O_WRONLY) mode |= FA_WRITE;
        if ((file->flags & O_ACCMODE) & O_RDWR) mode |= FA_WRITE;
         /* Opens the file, if it is existing. If not, a new file is created. */
        if (file->flags & O_CREAT) mode |= FA_OPEN_ALWAYS;
        /* Creates a new file. If the file is existing, it is truncated and overwritten. */
        if (file->flags & O_TRUNC) mode |= FA_CREATE_ALWAYS;
        /* Creates a new file. The function fails if the file is already existing. */
        if (file->flags & O_EXCL) mode |= FA_CREATE_NEW;

        /* alloc a fd */
        fd = (FIL*)ff_mem_malloc(sizeof(FIL));
        if (fd == NULL)
        {
            ff_mem_free(driver_fn);
            return -ENOMEM;
        }

        ret = f_open(fd, driver_fn, mode);
        ff_mem_free(driver_fn);
        if (ret == FR_OK)
        {
            file->pos = fd->fptr;
            file->size = f_size(fd);
            file->data = fd;
            if (file->flags & O_APPEND)
            {
                f_lseek(fd, f_size(fd));
                file->pos = fd->fptr;
            }
        }
        else
        {
            ff_mem_free(fd);
            return ff_dfs_result_to_dfs(ret);
        }
    }

    return 0;
}

static int ff_dfs_file_close(struct dfs_fd *file)
{
    DIR *dir;
    FIL *fd;
    FRESULT ret = FR_OK;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "close file or file->data NULL");
        return -EINVAL;
    }

    if (file->type == FT_DIRECTORY)
    {
        dir = (DIR*)(file->data);
        ff_mem_free(dir);
        file->data = NULL;
    }
    else if (file->type == FT_REGULAR)
    {
        fd = (FIL*)(file->data);

        ret = f_close(fd);
        if (ret == FR_OK)
        {
            ff_mem_free(fd);
            file->data = NULL;
        }
    }

    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_file_ioctl(struct dfs_fd *file, int cmd, void *args)
{
    FIL *fd;
    FSIZE_t fptr, len;
    FRESULT ret;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "ioctl file or file->data NULL");
        return -EINVAL;
    }

    switch (cmd)
    {
    case DFS_FIOFTRUNCATE:
        fd = (FIL*)(file->data);
        fptr = fd->fptr;
        len = *(off_t*)(args);
        if (len <= fd->obj.objsize)
        {
            fd->fptr = len;
            ret = f_truncate(fd);
        }
        else
        {
            ret = f_lseek(fd, len);
        }
        fd->fptr = fptr;
        return ff_dfs_result_to_dfs(ret);
        // break;
    default:
        break;
    }

    return -ENOSYS;
}

static int ff_dfs_file_read(struct dfs_fd *file, void *buf, size_t len)
{
    FIL *fd;
    FRESULT ret;
    UINT bytes;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "read file or file->data NULL");
        return -EINVAL;
    }
    
    if (file->type == FT_DIRECTORY)
    {
        return -EISDIR;
    }

    fd = (FIL*)(file->data);
    ret = f_read(fd, buf, len, &bytes);
    if (ret == FR_OK)
    {
        return bytes;
    }

    return ff_dfs_result_to_dfs(ret);  
}

static int ff_dfs_file_write(struct dfs_fd *file, const void *buf, size_t len)
{
    FIL *fd;
    FRESULT ret;
    UINT bytes;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "write file or file->data NULL");
        return -EINVAL;
    }

    if (file->type == FT_DIRECTORY)
    {
        return -EISDIR;
    }

    fd = (FIL*)(file->data);
    ret = f_write(fd, buf, len, &bytes);
    file->pos = fd->fptr;
    file->size = f_size(fd);
    if (ret == FR_OK)
    {
        return bytes;
    }

    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_file_flush(struct dfs_fd *file)
{
    FIL *fd;
    FRESULT ret;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "flush file or file->data NULL");
        return -EINVAL;
    }

    fd = (FIL*)(file->data);
    ret = f_sync(fd);

    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_file_lseek(struct dfs_fd *file, off_t offset)
{
    FIL *fd;
    DIR *dir;
    FRESULT ret = FR_OK;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "lseek file or file->data NULL");
        return -EINVAL;
    }

    if (file->type & FT_REGULAR)
    {
        fd = (FIL*)(file->data);
        ret = f_lseek(fd, offset);
        if (ret == FR_OK)
        {
            file->pos = fd->fptr;
            return fd->fptr;
        }
    }
    else if (file->type & FT_DIRECTORY)
    {
        dir = (DIR*)(file->data);
        ret = f_seekdir(dir, offset);
        if (ret == FR_OK)
        {
            file->pos = offset;
            return file->pos;
        }
    }

    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_file_getdents(struct dfs_fd *file, struct dirent *dirp, uint32_t count)
{
    DIR *dir;
    FILINFO fno;
    FRESULT ret;
    struct dirent *d;
    char *fn;
    uint32_t idx;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "getdents file or file->data NULL");
        return -EINVAL;
    }

    dir = (DIR*)(file->data);

    /* make integer count */
    count = (count / sizeof(struct dirent)) * sizeof(struct dirent);
    if (count == 0) return -EINVAL;

    ret = FR_OK;
    idx = 0;
    while (1)
    {
        d = dirp + idx;
        ret = f_readdir(dir, &fno);
        if (ret != FR_OK || fno.fname[0] == 0)
        {
            break;
        }

    #if FF_USE_LFN
        fn = *fno.fname ? fno.fname : fno.altname;
    #else
        fn = fno.fname;
    #endif

        d->d_type = 0;
        if (fno.fattrib & AM_DIR) d->d_type = 0x02; // dir
        else d->d_type = 0x01;  // reg

        d->d_namlen = (uint8_t)strlen(fn);
        d->d_reclen = (uint16_t)sizeof(struct dirent);
        strncpy(d->d_name, fn, strlen(fn) + 1);

        idx ++;
        if (idx * sizeof(struct dirent) >= count) break;
    }
    
    if (idx == 0)
    {
        return ff_dfs_result_to_dfs(ret);
    }
    
    file->pos += idx * sizeof(struct dirent);

    return idx * sizeof(struct dirent);
}


/* static func - filesystem */

static int ff_dfs_fs_mount(struct dfs_filesystem *fs, unsigned long rwflag, const void *data)
{
    FATFS *fat;
    DIR *dir;
    FRESULT ret;
    int vol;
    char drive[8];
    char logic_nbr[4] = {'0', ':' };
    // char logic_nbr[4] = "0:/";

    if (fs == NULL || fs->dev_name == NULL)
    {
        LOG_E(TAG, "mount fs or dev->name NULL");
        return -EINVAL;
    }

    /* get vol number by port name */
    vol = disk_get_vol_by_name(fs->dev_name);
    if (vol < 0)
    {
        LOG_E(TAG, "mount get disk '%s' fail (%d)", fs->dev_name, vol);
        return -ENOENT;
    }

    logic_nbr[0] = '0' + vol;

    fat = (FATFS*)ff_mem_malloc(sizeof(FATFS));
    if (fat == NULL)
    {
        return -ENOMEM;
    }

    ret = f_mount(fat, (const TCHAR *)logic_nbr, 1);
    // LOG_I(TAG, "mount '%s' ret = %d", logic_nbr, ret);
    if (ret == FR_OK)
    {
        snprintf(drive, sizeof(drive), "%d:/", vol);
        dir = (DIR *)ff_mem_malloc(sizeof(DIR));
        if (dir == NULL)
        {
            f_mount(NULL, (const TCHAR *)logic_nbr, 1);
            ff_mem_free(fat);
            return -ENOMEM;
        }

        /* open the root directory to test whether the fatfs is valid */
        ret = f_opendir(dir, drive);
        if (ret != FR_OK) goto _exit;

        fs->data = fat;
        ff_mem_free(dir);
        return 0;
    }

_exit:
    f_mount(NULL, (const TCHAR *)logic_nbr, 1);
    ff_mem_free(fat);
    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_fs_unmount(struct dfs_filesystem *fs)
{
    FATFS *fat;
    FRESULT ret;
    int vol;
    char logic_nbr[2] = {'0', ':'};

    if (fs == NULL || fs->data == NULL)
    {
        LOG_E(TAG, "unmount fs or fs->data NULL");
        return -EINVAL;
    }

    vol = disk_get_vol_by_name(fs->dev_name);
    if (vol < 0)
    {
        LOG_E(TAG, "unmount get disk '%s' fail (%d)", fs->dev_name, vol);
        return -ENOENT;
    }

    fat = (FATFS*)(fs->data);

    logic_nbr[0] = '0' + vol;
    ret = f_mount(NULL, logic_nbr, (BYTE)1);
    if (ret != FR_OK)
    {
        return ff_dfs_result_to_dfs(ret);
    }

    fs->data = NULL;
    ff_mem_free(fat);

    return 0;
}

static int ff_dfs_fs_mkfs(const char *dev_name)
{
    BYTE *work;
    FRESULT ret;
    int vol;
    char logic_nbr[2] = {'0', ':'};

    if (dev_name == NULL)
    {
        LOG_E(TAG, "mkfs dev_name null");
        return -EINVAL;
    }

    work = (BYTE*)ff_mem_malloc(FF_MAX_SS);
    if (work == NULL) return -ENOMEM;

    vol = disk_get_vol_by_name(dev_name);
    if (vol < 0)
    {
        LOG_W(TAG, "disk '%s' not found or support!", dev_name);
        return -ENOENT;
    }
    else
    {
        logic_nbr[0] = '0' + vol;
    }
    
    ret = f_mkfs(logic_nbr, FM_ANY|FM_SFD, 0, work, FF_MAX_SS);
    ff_mem_free(work);
    work = NULL;

    if (ret != FR_OK)
    {
        LOG_E(TAG, "format error");
        return ff_dfs_result_to_dfs(ret);
    }

    return 0;
}

static int ff_dfs_fs_statfs(struct dfs_filesystem *fs, struct statfs *buf)
{
    FATFS *fat;
    FRESULT ret;
    char driver[4];
    DWORD fre_clust, fre_sect, tot_sect;

    if (fs == NULL || buf == NULL || fs->data == NULL)
    {
        LOG_E(TAG, "staffs fs or fs->data or buf null");
        return -EINVAL;
    }

    fat = (FATFS*)fs->data;
    snprintf(driver, sizeof(driver), "%d:", fat->pdrv);
    ret = f_getfree(driver, &fre_clust, &fat);
    if (ret != FR_OK) return ff_dfs_result_to_dfs(ret);

    tot_sect = (fat->n_fatent - 2) * fat->csize;
    fre_sect = fre_clust * fat->csize;

    buf->f_bfree = fre_sect;
    buf->f_blocks = tot_sect;
#if (FF_MAX_SS != 512)
    buf->f_bsize = fat->sszie;
#else
    buf->f_bsize = 512;
#endif

    return 0;
}

static int ff_dfs_fs_unlink(struct dfs_filesystem *fs, const char *path)
{
    FRESULT ret;
    int vol;
    char *drivers_fn;

    if (fs == NULL || fs->data == NULL || path == NULL)
    {
        LOG_E(TAG, "unlink fs or fs->data or path null");
        return -EINVAL;
    }

    vol = disk_get_vol_by_name(fs->dev_name);
    if (vol < 0) return -ENOENT;

    drivers_fn = (char *)ff_mem_malloc(256);
    if (drivers_fn == NULL) return -ENOMEM;

    snprintf(drivers_fn, 256, "%d:%s", vol, path);

    ret = f_unlink(drivers_fn);
    ff_mem_free(drivers_fn);

    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_fs_stat(struct dfs_filesystem *fs, const char *path, struct stat *st)
{
    FRESULT ret;
    FILINFO info;
    int vol;
    char *drivers_fn;

    if (fs == NULL || fs->dev_name == NULL || path == NULL || st == NULL)
    {
        LOG_E(TAG, "unlink fs or fs->data or path or st null");
        return -EINVAL;
    }

    vol = disk_get_vol_by_name(fs->dev_name);
    if (vol < 0) return -ENOENT;

    drivers_fn = (char *)ff_mem_malloc(256);
    if (drivers_fn == NULL) return -ENOMEM;

    snprintf(drivers_fn, 256, "%d:%s", vol, path);

    ret = f_stat(drivers_fn, &info);
    ff_mem_free(drivers_fn);
    if (ret == FR_OK)
    {
        /* convert to dfs stat structure */
        st->st_dev = 0;

        st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH |
                      S_IWUSR | S_IWGRP | S_IWOTH;
        if (info.fattrib & AM_DIR)
        {
            st->st_mode &= ~S_IFREG;
            st->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
        }
        if (info.fattrib & AM_RDO)
        {
            st->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
        }

        st->st_size = info.fsize;

        /* get st_mtime */
        {
            struct tm tm_file;
            int year, mon, day, hour, min, sec;
            WORD tmp;

            tmp = info.fdate;
            day = tmp & 0x1F;           /* bit[4:0] Day(1..31) */
            tmp >>= 5;
            mon = tmp & 0x0F;           /* bit[8:5] Month(1..12) */
            tmp >>= 4;
            year = (tmp & 0x7F) + 1980; /* bit[15:9] Year origin from 1980(0..127) */

            tmp = info.ftime;
            sec = (tmp & 0x1F) * 2;     /* bit[4:0] Second/2(0..29) */
            tmp >>= 5;
            min = tmp & 0x3F;           /* bit[10:5] Minute(0..59) */
            tmp >>= 6;
            hour = tmp & 0x1F;          /* bit[15:11] Hour(0..23) */

            memset(&tm_file, 0, sizeof(tm_file));
            tm_file.tm_year = year - 1900; /* Years since 1900 */
            tm_file.tm_mon  = mon - 1;     /* Months *since* january: 0-11 */
            tm_file.tm_mday = day;         /* Day of the month: 1-31 */
            tm_file.tm_hour = hour;        /* Hours since midnight: 0-23 */
            tm_file.tm_min  = min;         /* Minutes: 0-59 */
            tm_file.tm_sec  = sec;         /* Seconds: 0-59 */

            st->st_mtime = mktime(&tm_file);
        }
    }

    return ff_dfs_result_to_dfs(ret);
}

static int ff_dfs_fs_rename(struct dfs_filesystem *fs, const char *oldpath, const char *newpath)
{
    FRESULT ret;
    int vol;
    char *drivers_oldfn;
    const char *drivers_newfn;

    if (fs == NULL || fs->dev_name == NULL || oldpath == NULL || newpath == NULL)
    {
        LOG_E(TAG, "unlink fs or fs->data or oldpath or newpath null");
        return -EINVAL;
    }

    vol = disk_get_vol_by_name(fs->dev_name);
    if (vol < 0) return -ENOENT;

    drivers_newfn = newpath;
    drivers_oldfn = (char *)ff_mem_malloc(256);
    if (drivers_oldfn == NULL) return -ENOMEM;

    snprintf(drivers_oldfn, 256, "%d:%s", vol, oldpath);

    ret = f_rename(drivers_oldfn, drivers_newfn);
    ff_mem_free(drivers_oldfn);

    return ff_dfs_result_to_dfs(ret);
}



/* export func */

static const struct dfs_file_ops ff_dfs_file_ops = 
{
    .open = ff_dfs_file_open,
    .close = ff_dfs_file_close,
    .ioctl = ff_dfs_file_ioctl,
    .read = ff_dfs_file_read,
    .write = ff_dfs_file_write,
    .flush = ff_dfs_file_flush,
    .lseek = ff_dfs_file_lseek,
    .getdents = ff_dfs_file_getdents,
};

static const struct dfs_filesystem_ops ff_dfs_fs_ops = 
{
    .name = "fatfs",
    .flags = DFS_FS_FLAG_DEFAULT,
    .fops = &ff_dfs_file_ops,

    .mount = ff_dfs_fs_mount,
    .unmount = ff_dfs_fs_unmount,
    .mkfs = ff_dfs_fs_mkfs,
    .statfs = ff_dfs_fs_statfs,
    .unlink = ff_dfs_fs_unlink,
    .stat = ff_dfs_fs_stat,
    .rename = ff_dfs_fs_rename,
};


int ff_port_dfs_register(void)
{
    int sta;

    sta = dfs_fs_register(&ff_dfs_fs_ops);
    if (sta == 0)
    {
        // LOG_I(TAG, "filesystem '%s' register OK", ff_dfs_fs_ops.name);
    }
    else
    {
        LOG_E(TAG, "filesystem '%s' register fail", ff_dfs_fs_ops.name);
    }
    return 0;
}





